package worker

import (
	"fmt"
	"go_job/common"
	"time"
)

type Scheduler struct {
	jobEventChan  chan *common.JobEvent
	jobExeResChan chan *common.JobExecuteResult
	jobPlanTable  map[string]*common.JobSchedulePlan
	executeInfo   map[string]*common.JobExecuteInfo
}

var (
	G_Scheduler *Scheduler
)

func InitScheduler() (err error) {
	G_Scheduler = &Scheduler{
		jobEventChan:  make(chan *common.JobEvent, 1000),
		jobExeResChan: make(chan *common.JobExecuteResult, 1000),
		jobPlanTable:  make(map[string]*common.JobSchedulePlan),
		executeInfo:   make(map[string]*common.JobExecuteInfo),
	}
	//启动调度协程
	go G_Scheduler.SchedulerLoop()

	fmt.Println("初始化调度模块完成")

	return
}

func (scheduler *Scheduler) SchedulerLoop() {
	var (
		jobEvent      *common.JobEvent
		sleepTime     time.Duration
		scheduleTimer *time.Timer
		jobRes        *common.JobExecuteResult
	)
	sleepTime = scheduler.TrySchedule()

	scheduleTimer = time.NewTimer(sleepTime)

	for {
		select {

		case jobEvent = <-scheduler.jobEventChan:
			scheduler.HandleJobEvent(jobEvent)
		case <-scheduleTimer.C:

		case jobRes = <-scheduler.jobExeResChan:
			scheduler.HandleJobResult(jobRes)
		}
		//进行一次任务调度
		sleepTime = scheduler.TrySchedule()
		//重置定时器
		scheduleTimer.Reset(sleepTime)
	}
}

//计算任务调度状态

func (scheduler *Scheduler) TrySchedule() (after time.Duration) {
	var (
		jobPlan  *common.JobSchedulePlan
		now      time.Time
		nearTime *time.Time
	)
	now = time.Now()

	if len(scheduler.jobPlanTable) == 0 {
		after = 1 * time.Second
		return
	}

	//遍历所有任务
	for _, jobPlan = range scheduler.jobPlanTable {
		if jobPlan.NextTime.Before(now) || jobPlan.NextTime.Equal(now) {
			//尝试执行任务
			scheduler.TryStartJob(jobPlan)
			jobPlan.NextTime = jobPlan.Expr.Next(now) //更新下次执行时间
		}
		//统计最近要过期的任务(sleep N)
		if nearTime == nil || jobPlan.NextTime.Before(*nearTime) {
			nearTime = &jobPlan.NextTime
		}

	}
	after = (*nearTime).Sub(now)
	return
}

func (scheduler *Scheduler) PushJobEvent(jobEvent *common.JobEvent) {
	G_Scheduler.jobEventChan <- jobEvent
}

func (scheduler *Scheduler) HandleJobEvent(jobEvent *common.JobEvent) {
	var (
		err            error
		plan           *common.JobSchedulePlan
		jobExist       bool
		jobExecuting   bool
		jobExecuteInfo *common.JobExecuteInfo
	)

	switch jobEvent.EventType {
	case common.JOB_EVENT_SAVE:
		if plan, err = common.BuildCronPlan(jobEvent.Job); err != nil {
			return
		}
		scheduler.jobPlanTable[jobEvent.Job.Name] = plan
		break
	case common.JOB_EVENT_DELETE:
		if plan, jobExist = scheduler.jobPlanTable[jobEvent.Job.Name]; jobExist {
			delete(scheduler.jobPlanTable, jobEvent.Job.Name)
		}
		break
	case common.JOB_EVENT_KILL:
		//取消command执行
		if jobExecuteInfo, jobExecuting = scheduler.executeInfo[jobEvent.Job.Name]; jobExecuting {
			jobExecuteInfo.CancelFun()
		}
	}
}

//尝试执行任务
func (scheduler *Scheduler) TryStartJob(jobPlan *common.JobSchedulePlan) {
	//任务可能会执行很久,在上次运行完之前不能再执行
	var (
		execute      *common.JobExecuteInfo
		jobExecuting bool
	)

	//如果job正在执行 跳过
	if execute, jobExecuting = scheduler.executeInfo[jobPlan.Job.Name]; jobExecuting {
		fmt.Println("任务尚未推出,结束本次执行")
		return
	}

	//构建执行状态
	execute = common.BuildJobExecuteInfo(jobPlan)

	scheduler.executeInfo[jobPlan.Job.Name] = execute

	//执行任务
	fmt.Println("开始执行任务", jobPlan.Job.Name, execute.PlanTime, execute.RealTime)
	G_executor.ExecuteJob(execute)
}

func (scheduler *Scheduler) PushJobExeResult(exe *common.JobExecuteResult) {
	scheduler.jobExeResChan <- exe
}

func (scheduler *Scheduler) HandleJobResult(result *common.JobExecuteResult) {
	var (
		jobLog *common.JobLog
	)
	delete(scheduler.executeInfo, result.ExecuteInfo.Job.Name)
	//生成日志
	if result.Err != common.ERR_LOCAL_ALREADY_IN_USE {
		jobLog = &common.JobLog{
			JobName:      result.ExecuteInfo.Job.Name,
			Command:      result.ExecuteInfo.Job.Command,
			Output:       string(result.OutPut),
			PlanTime:     result.ExecuteInfo.PlanTime.UnixNano() / 1000 / 1000,
			ScheduleTime: result.ExecuteInfo.RealTime.UnixNano() / 1000 / 1000,
			StartTime:    result.StartTime.UnixNano() / 1000 / 1000,
			EndTime:      result.EndTime.UnixNano() / 1000 / 1000,
		}
		if result.Err != nil {
			jobLog.Err = result.Err.Error()
		} else {
			jobLog.Err = ""
		}
	}
	G_logSink.PushToLogChan(jobLog)
}
